/*
 * Copyright (C) 2012-2013 Michael L.R. Marques
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 * 
 * Contact: michaellrmarques@gmail.com
 */

package com.jm.commons.components.editor;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JEditorPane;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.UndoableEditEvent;
import javax.swing.event.UndoableEditListener;
import javax.swing.text.Document;
import javax.swing.undo.UndoManager;

/**
 *
 * @created Nov 30, 2012
 * @author Michael L.R. Marques
 */
public class JMEditorPane extends JEditorPane implements UndoableEditListener, PropertyChangeListener, DocumentListener {
    
    /**
     * 
     */
    private static final int PAGE_LIMIT = 10;
    
    /**
     * 
     */
    private UndoManager undoManager;
    
    /**
     * 
     */
    private List<URL> pageHistory;
    
    /**
     * 
     */
    private int page;
    
    /**
     * 
     */
    public JMEditorPane() {
        super();
        this.undoManager = new UndoManager();
        this.undoManager.setLimit(100);
        
        this.pageHistory = new ArrayList();
        this.page = -1;
        
        this.addPropertyChangeListener(this);
    }
    
    /**
     * 
     * @param page
     * @throws IOException 
     */
    @Override public void setPage(String page) {
        // Fire page opening event
        firePageOpening(new HtmlPageEvent(this, page, HtmlPageEvent.EventType.OPENING));
        try {
            // Set the page
            super.setPage(page);
            // Add the page to the page collection
            try {
                addPage(new URI(page).toURL());
            } catch (URISyntaxException urie) {
                urie.printStackTrace();
            }
            // Fire page opened event
            firePageOpened(new HtmlPageEvent(this, this.getPage(), HtmlPageEvent.EventType.OPENED));
        } catch (IOException ioe) {
            ioe.printStackTrace();
        }
    }
    
    /**
     * 
     * @param page
     * @throws IOException 
     */
    @Override public void setPage(URL page) {
        firePageOpening(new HtmlPageEvent(this, page, HtmlPageEvent.EventType.OPENING));
        try {
            super.setPage(page);
            addPage(page);
            firePageOpened(new HtmlPageEvent(this, this.getPage(), HtmlPageEvent.EventType.OPENED));
        } catch (IOException ioe) {
            ioe.printStackTrace();
        }
    }
    
    /**
     * 
     * @param page
     * @throws IOException 
     */
    public void setPage(File page) {
        firePageOpening(new HtmlPageEvent(this, page, HtmlPageEvent.EventType.OPENING));
        super.setText("");
        try {
            super.setPage(page.toURI().toURL());
            addPage(page.toURI().toURL());
            firePageOpened(new HtmlPageEvent(this, this.getPage(), HtmlPageEvent.EventType.OPENED));
        } catch (IOException ioe) {
            ioe.printStackTrace();
        }
    }
    
    /**
     * 
     */
    public void back() {
        if (canGoBack()) {
            super.setText("");
            try {
                super.setPage(this.pageHistory.get(--this.page));
                firePageBack(new HtmlPageEvent(this, this.getPage(), HtmlPageEvent.EventType.BACK));
            } catch (IOException ioe) {
                ioe.printStackTrace();
            }
        }
    }
    
    /**
     * 
     */
    public void forward() {
        if (canGoForward()) {
            super.setText("");
            try {
                super.setPage(this.pageHistory.get(++this.page));
                firePageForward(new HtmlPageEvent(this, this.getPage(), HtmlPageEvent.EventType.FORWARD));
            } catch (IOException ioe) {
                ioe.printStackTrace();
            }
        }
    }
    
    /**
     * 
     * @return 
     */
    public boolean canGoBack() {
        return this.page > 0;
    }
    
    /**
     * 
     * @return 
     */
    public boolean canGoForward() {
        return this.page < (this.pageHistory.size()-1);
    }
    
    /**
     * 
     * @param page 
     */
    private void addPage(URL page) {
        // Dont add duplicate url to history
        if (this.page >= 0 &&
                this.pageHistory.get(this.page).equals(page)) {
            return;
        }
        // Add the new url to the page collection
        this.pageHistory.add(page);
        this.page++;
        // Check if we have reached the page limit
        if (this.pageHistory.size() >= PAGE_LIMIT) {
            this.pageHistory.remove(0);
            this.page--;
        }
        // Check if there are any pages infront of our current page and remove
        while (this.page < (this.pageHistory.size()-1)) {
            this.pageHistory.remove(this.pageHistory.size()-1);
        }
    }
    
    /**
     * 
     * @param listener 
     */
    public void addHtmlViewerListner(HtmlPageListener listener) {
        super.listenerList.add(HtmlPageListener.class, listener);
    }
    
    /**
     * 
     * @param listener 
     */
    public void removeHtmlViewerListner(HtmlPageListener listener) {
        super.listenerList.remove(HtmlPageListener.class, listener);
    }
    
    /**
     * 
     * @param e 
     */
    public void firePageOpening(HtmlPageEvent e) {
        // Loop through the listeners of type HtmlViewerListener
        for (HtmlPageListener listener : super.listenerList.getListeners(HtmlPageListener.class)) {
            // Call event
            listener.pageOpening(e);
        }
    }
    
    /**
     * 
     * @param e 
     */
    public void firePageOpened(HtmlPageEvent e) {
        // Loop through the listeners of type HtmlViewerListener
        for (HtmlPageListener listener : super.listenerList.getListeners(HtmlPageListener.class)) {
            // Call event
            listener.pageOpened(e);
        }
    }
    
    /**
     * 
     * @param e 
     */
    public void firePageBack(HtmlPageEvent e) {
        // Loop through the listeners of type HtmlViewerListener
        for (HtmlPageListener listener : super.listenerList.getListeners(HtmlPageListener.class)) {
            // Call event
            listener.pageBack(e);
        }
    }
    
    /**
     * 
     * @param e 
     */
    public void firePageForward(HtmlPageEvent e) {
        // Loop through the listeners of type HtmlViewerListener
        for (HtmlPageListener listener : super.listenerList.getListeners(HtmlPageListener.class)) {
            // Call event
            listener.pageForward(e);
        }
    }
    
    /**
     * 
     * @return 
     */
    public boolean canUndo() {
        return this.undoManager.canUndo();
    }
    
    /**
     * 
     */
    public void undo() {
        if (this.undoManager.canUndo()) {
            String value = this.getText();
            this.undoManager.undo();
            fireUndoEvent(value, this.getText());
        }
    }
    
    /**
     * 
     * @return 
     */
    public boolean canRedo() {
        return this.undoManager.canRedo();
    }
    
    /**
     * 
     */
    public void redo() {
        if (this.undoManager.canRedo()) {
            String value = this.getText();
            this.undoManager.redo();
            fireRedoEvent(value, this.getText());
        }
    }
    
    /**
     * 
     * @param e 
     */
    @Override public void undoableEditHappened(UndoableEditEvent e) {
        this.undoManager.addEdit(e.getEdit());
    }
    
    /**
     * 
     * @param listener 
     */
    public void addUndoRedoListener(UndoRedoListener listener) {
        super.listenerList.add(UndoRedoListener.class, listener);
    }
    
    /**
     * 
     * @param listener 
     */
    public void redoUndoRedoListener(UndoRedoListener listener) {
        super.listenerList.add(UndoRedoListener.class, listener);
    }
    
    /**
     * 
     * @param newValue
     * @param oldValue 
     */
    public void fireUndoEvent(String newValue, String oldValue) {
        // Loop through the listeners of type UndoRedoListener
        for (UndoRedoListener listener : getListeners(UndoRedoListener.class)) {
            // Call event
            listener.undoPerformed(new UndoRedoEvent(this, this.undoManager.canUndo(), this.undoManager.canRedo(), oldValue, newValue, UndoRedoEvent.EventType.UNDO));
        }
    }
    
    /**
     * 
     * @param newValue
     * @param oldValue 
     */
    public void fireRedoEvent(String newValue, String oldValue) {
        // Loop through the listeners of type UndoRedoListener
        for (UndoRedoListener listener : getListeners(UndoRedoListener.class)) {
            // Call event
            listener.redoPerformed(new UndoRedoEvent(this, this.undoManager.canUndo(), this.undoManager.canRedo(), oldValue, newValue, UndoRedoEvent.EventType.REDO));
        }
    }
    
    /**
     * 
     * @param newValue
     * @param oldValue 
     */
    public void fireChangeEvent(String newValue, String oldValue) {
        // Loop through the listeners of type UndoRedoListener
        for (UndoRedoListener listener : getListeners(UndoRedoListener.class)) {
            // Call event
            listener.changePerformed(new UndoRedoEvent(this, this.undoManager.canUndo(), this.undoManager.canRedo(), oldValue, newValue, UndoRedoEvent.EventType.CHANGE));
        }
    }
    
    /**
     * 
     * @param evt 
     */
    @Override public void propertyChange(PropertyChangeEvent evt) {
        if (evt.getPropertyName().equals("document")) {
            ((Document) evt.getNewValue()).addUndoableEditListener(this);
            ((Document) evt.getOldValue()).removeUndoableEditListener(this);
            ((Document) evt.getNewValue()).addDocumentListener(this);
            ((Document) evt.getOldValue()).removeDocumentListener(this);
        }
    }
    
    /**
     * Not using
     * @param e 
     */
    @Override public void insertUpdate(DocumentEvent e) {
        fireChangeEvent(this.getText(), this.getText());
    }
    
    /**
     * NOt using
     * @param e 
     */
    @Override public void removeUpdate(DocumentEvent e) {
        fireChangeEvent(this.getText(), this.getText());
    }
    
    /**
     * 
     * @param e 
     */
    @Override public void changedUpdate(DocumentEvent e) {
        fireChangeEvent(this.getText(), this.getText());
    }

}
